/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package View.ClusterizationPanel;

import View.ClusterizationPanel.ClusterizationWindow;
import java.awt.*;
//import Model.*;
import javax.swing.*;
import java.awt.event.*;
import com.sun.j3d.utils.geometry.*;
import com.sun.j3d.utils.universe.*;
import com.sun.j3d.utils.behaviors.mouse.*;
//import java.util.ArrayList;
import javax.media.j3d.*;
import javax.vecmath.*;
/**
 *
 * @author vf
 */
public class View3DPanel extends JPanel{

    public  ClusterizationWindow main;
    private Canvas3D canvas3D;
    private SimpleUniverse simpleU;
    private float axisLength;
    private float axisWidth;
    private float objSize;
    private float gridDensity;
    private float gridWidth;
    private double distanceChamp;
    private int NActions;
    private int NClusters;
    private float textScale;
    private float textOffset;
    private float selectionArrowsScale;

    private JPopupMenu popupMenu;
    private JMenuItem menuGrid;
    private JMenuItem menuAxis;
    private JSlider zoomSlider;
    private int sliderWidth;
    private JTable tableCentroids;
    private JScrollPane scrollPaneCentroids;

    RenderingAttributes gridRendering;
    RenderingAttributes axisRendering;

    RenderingAttributes[] centroidsSelectRA;
    RenderingAttributes[] actionsSelectRA;

    TransformGroup cameraTG;

    public View3DPanel(){main=null;}

    public void setParent(final ClusterizationWindow main){
        this.main = main;

        setLayout(new BorderLayout());

        canvas3D = new Canvas3D(SimpleUniverse.getPreferredConfiguration());
        //canvas3D.setSize(640,480);
        add(canvas3D, BorderLayout.CENTER);

        simpleU = new SimpleUniverse(canvas3D);
        //this.setMinimumSize(new Dimension(640,480));
        //this.setPreferredSize(new Dimension(640,480));
        //this.setSize(new Dimension(640,480));

    }
       

    public void addSceneView(){
        axisLength = 4f;
        axisWidth = 2f;
        gridDensity = axisLength/2;//Que ça tombe juste, de préférence
        gridWidth = 2f;
        objSize = 0.05f;
        distanceChamp = 20;
        textScale = 0.2f;
        selectionArrowsScale = 0.1f;
        textOffset = 0.2f;

        //Permet au menu popup d'apparaitre par dessus la scène
        //Le canvas 3D est heavyweight (se met toujours au premier plan)
        //-> popup menu doit être heavy aussi
        JPopupMenu.setDefaultLightWeightPopupEnabled(false);


        // Menu popup
        popupMenu = new JPopupMenu();
        menuGrid = new JMenuItem("Show grid");
        menuAxis = new JMenuItem("Hide axis");

        menuGrid.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            menuGridAction();
          }
        });

        menuAxis.addActionListener(new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            menuAxisAction();
          }
        });

        popupMenu.add(menuGrid);
        popupMenu.add(menuAxis);

        // Mouse listener sur la scène
        canvas3D.addMouseListener(new MouseAdapter() {
          public void mouseReleased(MouseEvent e) {
            mouseReleasedAction(e);
          }
        });

        //La commande rendering.setVisible(!rendering.getVisible())
        //permet d'afficher/masquer la grille ou les axes
        //Rajouter le rendering attribute à l'apparence de l'objet à rendre invisible
        gridRendering = new RenderingAttributes();
        gridRendering.setCapability(RenderingAttributes.ALLOW_VISIBLE_READ);
        gridRendering.setCapability(RenderingAttributes.ALLOW_VISIBLE_WRITE);
        gridRendering.setVisible(false);
        
        axisRendering = new RenderingAttributes();
        axisRendering.setCapability(RenderingAttributes.ALLOW_VISIBLE_READ);
        axisRendering.setCapability(RenderingAttributes.ALLOW_VISIBLE_WRITE);

        //Slider de zoom
        zoomSlider = new JSlider(SwingConstants.VERTICAL);
        this.add(zoomSlider, BorderLayout.WEST);
        sliderWidth = zoomSlider.getWidth();
        zoomSlider.addMouseMotionListener(new MouseMotionAdapter() {
            public void mouseDragged(MouseEvent e) {
                zoomSliderAction();
            }
        });
        compileScene();
    }

    public void compileScene(){
        simpleU.cleanup();
        simpleU = new SimpleUniverse(canvas3D);

        //Initialisation de la vue
        cameraTG = simpleU.getViewingPlatform().getViewPlatformTransform();
        Transform3D cameraTransform = new Transform3D();
        cameraTransform.setTranslation(new Vector3f(0.0f, 0.0f, 7.0f));
        cameraTG.setTransform(cameraTransform);

        //Profondeur du champ de vision
        simpleU.getViewer().getView().setBackClipDistance(distanceChamp);

        NActions = main.getNumberOfActions();
        NClusters = main.getNumberOfClusters();
        //Creation de la scène
        BranchGroup scene = createScene();
        System.out.println("Done creating scene");
        scene.compile();
        simpleU.addBranchGraph(scene);

        //updateCentroidsTable();
    }

    public BranchGroup createScene() {
        // Creation de l'objet parent qui contiendra tous les autres objets 3D
        BranchGroup parent = new BranchGroup();

        // Construction de l'arriere plan de couleur blanche et definition de son rayon d'action
        Background background = new Background(new Color3f(Color.white));
        background.setApplicationBounds(new BoundingBox());
        parent.addChild(background);

        //Creation du transformGroup auquel vont s'appliquer les actions de la souris
        TransformGroup mouseTransform = new TransformGroup();

        //Creation d'un branchgroup contenant tous les objets de la scène
        BranchGroup objBranch = new BranchGroup();

        //Creation d'une lumière unidirectionnelle
        Light directionalLight = new DirectionalLight(
            new Color3f (1,  1,  1),
            new Vector3f(0, 0, -1));
        //agit sur une sphere de rayon 10 centrée à l'origine
        directionalLight.setInfluencingBounds(new BoundingSphere(new Point3d(0,0,0), 10));

        parent.addChild(directionalLight);

        //Création d'un branchGroup contenant toutes les actions
        BranchGroup actionsBranch = new BranchGroup();


        //Création des sphères représentant les objets
        for (int i=0;i<NActions+NClusters;i++){
            float[] coords;
            Appearance appearance;
            Material colorMat;
            //Récupère les coordonnées des actions
            if(i<NActions)coords = main.getActionsCoords(i);
            else coords = main.getClusterCoords(i-NActions);

            //Creation de la transformation translation
            Transform3D translation = new Transform3D();
            translation.setTranslation(new Vector3f(coords[0], coords[1], coords[2]));
            //Création du transformgroup qui va contenir la translation
            TransformGroup translationTransform = new TransformGroup(translation);

            //Couleur
            appearance = new Appearance();
            Color color;
            if(i<NActions)color = main.getActionColor(i);
            else color = Color.LIGHT_GRAY;

            //Les diffuse color sont affectées par la lumière
            colorMat = new Material();
            colorMat.setDiffuseColor(new Color3f(color));

            appearance.setMaterial(colorMat);

            //Création d'une sphère de la couleur du cluster
            Sphere sphere = new Sphere(objSize,Primitive.GENERATE_NORMALS, 32, appearance);

            //Ajout de la sphere au transformgroup
            translationTransform.addChild(sphere);
            //Ajout du tranformgroup au branchGroup
            actionsBranch.addChild(translationTransform);
        }

        //Ajout du BranchGroup au mouseTransform
        objBranch.addChild(actionsBranch);

        BranchGroup axesGroup = new BranchGroup();

        //Axe X
        axesGroup.addChild(straightLine(new Point3f(-axisLength/2, 0.0f, 0.0f),
                                             new Point3f(axisLength/2, 0.0f, 0.0f),
                                             "solid", Color.black, axisWidth, axisRendering));
        //Axe Y
        axesGroup.addChild(straightLine(new Point3f(0.0f, -axisLength/2, 0.0f),
                                             new Point3f(0.0f, axisLength/2, 0.0f),
                                             "solid", Color.black, axisWidth, axisRendering));
        //Axe Z
        axesGroup.addChild(straightLine(new Point3f(0.0f, 0.0f, -axisLength/2),
                                             new Point3f(0.0f, 0.0f, axisLength/2),
                                             "solid", Color.black, axisWidth, axisRendering));

        //Labels des axes
        //textOffset aligne bien le texte avec le centre de l'axe
        axesGroup.addChild(
                billboardText("X",new Vector3f(axisLength/2+0.2f,0.0f,0.0f-textOffset),
                              textScale, axisRendering));

        axesGroup.addChild(
                billboardText("Y",new Vector3f(0.0f,axisLength/2+0.2f,0.0f-textOffset),
                              textScale, axisRendering));

        axesGroup.addChild(
                billboardText("Z",new Vector3f(0.0f,0.0f,axisLength/2+0.2f-textOffset),
                              textScale, axisRendering));

        objBranch.addChild(axesGroup);
        //Grille
        //On augmente le nombre de lignes de la grille en diminuant gridDensity
        BranchGroup gridGroup = new BranchGroup();
        for (int i=0;i<(int)(axisLength/gridDensity)+1;i++)
        {
            for (int j=0;j<(int)(axisLength/gridDensity)+1;j++)
            {
                //Lignes suivant Z
                gridGroup.addChild(straightLine(
                        new Point3f(-axisLength/2+i*gridDensity,
                                    -axisLength/2+j*gridDensity,
                                    -axisLength/2),
                        new Point3f(-axisLength/2+i*gridDensity,
                                    -axisLength/2+j*gridDensity,
                                    axisLength/2),
                        "dot", Color.black, gridWidth, gridRendering));
                //Lignes suivant Y
                gridGroup.addChild(straightLine(
                        new Point3f(-axisLength/2+i*gridDensity,
                                    -axisLength/2,
                                    -axisLength/2+j*gridDensity),
                        new Point3f(-axisLength/2+i*gridDensity,
                                    axisLength/2,
                                    -axisLength/2+j*gridDensity),
                        "dot", Color.black, gridWidth, gridRendering));
                //Lignes suivant X
                gridGroup.addChild(straightLine(
                        new Point3f(-axisLength/2,
                                    -axisLength/2+j*gridDensity,
                                    -axisLength/2+i*gridDensity),
                        new Point3f(axisLength/2,
                                    -axisLength/2+j*gridDensity,
                                    -axisLength/2+i*gridDensity),
                        "dot", Color.black, gridWidth, gridRendering));
            }
        }
        objBranch.addChild(gridGroup);

        BranchGroup selectArrowsGroup = new BranchGroup();
        //Fleches de selection des centroides
        centroidsSelectRA = new RenderingAttributes[NClusters];
        for (int i=0;i<NClusters;i++){
            centroidsSelectRA[i] = new RenderingAttributes();
            centroidsSelectRA[i].setCapability(RenderingAttributes.ALLOW_VISIBLE_READ);
            centroidsSelectRA[i].setCapability(RenderingAttributes.ALLOW_VISIBLE_WRITE);
            centroidsSelectRA[i].setVisible(false);
            float[] coords = main.getClusterCoords(i);
            selectArrowsGroup.addChild(selectArrows(new Vector3f(coords[0], coords[1], coords[2]), main.getClusterColor(i), selectionArrowsScale, centroidsSelectRA[i]));
        }

        //Fleches de selection des actions
        actionsSelectRA = new RenderingAttributes[NActions];
        for (int i=0;i<NActions;i++){
            actionsSelectRA[i] = new RenderingAttributes();
            actionsSelectRA[i].setCapability(RenderingAttributes.ALLOW_VISIBLE_READ);
            actionsSelectRA[i].setCapability(RenderingAttributes.ALLOW_VISIBLE_WRITE);
            actionsSelectRA[i].setVisible(false);
            float[] coords = main.getActionsCoords(i);
            selectArrowsGroup.addChild(selectArrows(new Vector3f(coords[0], coords[1], coords[2]), Color.BLACK, selectionArrowsScale, actionsSelectRA[i]));
        }

        objBranch.addChild(selectArrowsGroup);

        //Rotation initiale de la scène
        Transform3D rotationZ = new Transform3D();
        rotationZ.rotZ(5*Math.PI/4);

        Transform3D initialRotation = new Transform3D();
        initialRotation.rotX(-Math.PI/4);
        initialRotation.mul(rotationZ);

        TransformGroup objInitialTransform = new TransformGroup(initialRotation);
        objInitialTransform.addChild(objBranch);

        // Autorise la modification de tous les objets du "mousetransform"
        // après compilation de la scène
        mouseTransform.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
        mouseTransform.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

        // Création du comportement de rotation de la souris
        MouseRotate rotate = new MouseRotate(mouseTransform);
        rotate.setSchedulingBounds(new BoundingSphere());
        parent.addChild(rotate);

        // Création du comportement de déplacement de la souris
        MouseTranslate translate = new MouseTranslate(mouseTransform);
        translate.setSchedulingBounds(new BoundingSphere());
        parent.addChild(translate);

        /*// Création du comportement de zoom de la souris
        MouseZoom zoom = new MouseZoom(mouseTransform);
        zoom.setSchedulingBounds(new BoundingSphere());
        parent.addChild(zoom);*/

        mouseTransform.addChild(objInitialTransform);
        parent.addChild(mouseTransform);

        return parent;
     }

    private Shape3D straightLine(Point3f a, Point3f b, String pattern, Color color, float width, RenderingAttributes rendering) {
        Appearance app = new Appearance();
        //Permet de rendre l'objet invisible si necessaire
        if(rendering!=null) app.setRenderingAttributes(rendering);

        //Couleur
        ColoringAttributes ca = new ColoringAttributes(
                                    new Color3f(color),
                                    ColoringAttributes.SHADE_FLAT);
        app.setColoringAttributes(ca);

        //Epaisseur
        LineAttributes straightLa = new LineAttributes();
        straightLa.setLineWidth(width);
        app.setLineAttributes(straightLa);

        //Pattern
        if (pattern.equals("solid"))
            straightLa.setLinePattern(LineAttributes.PATTERN_SOLID);
        else if (pattern.equals("dot"))
            straightLa.setLinePattern(LineAttributes.PATTERN_DOT);
        else
        {
            System.out.println("Pattern de ligne non reconnnu. Solid par defaut.");
            straightLa.setLinePattern(LineAttributes.PATTERN_SOLID);
        }

        //Creation de la ligne
        Point3f[] linePts = new Point3f[2];
        linePts[0] = a;
        linePts[1] = b;
        LineArray sla = new LineArray(2, LineArray.COORDINATES);
        sla.setCoordinates(0, linePts);
        return new Shape3D(sla, app);
    }

    private TransformGroup billboardText(String text, Vector3f position, float sizeFactor, RenderingAttributes rendering)
    {
        //Apparence tu texte
        Appearance TextApp = new Appearance();
        //Permet de rendre l'objet invisible si necessaire
        if(rendering!=null) TextApp.setRenderingAttributes(rendering);

        TextApp.setColoringAttributes(new ColoringAttributes(
                                            new Color3f(Color.black),
                                            ColoringAttributes.SHADE_FLAT));

        //Police
        Font3D font3D = new Font3D(new Font("Arial", Font.PLAIN, 1),
                                   new FontExtrusion());

        Text3D text3D = new Text3D(font3D, text,
                           new Point3f(0.0f, 0.0f, 0.0f),
                           Text3D.ALIGN_CENTER,
                           Text3D.PATH_RIGHT);

        //Reduit (ou agrandit) la taille du texte + translation
        Transform3D Transf3D = new Transform3D();
        Transf3D.setScale(sizeFactor);
        Transf3D.setTranslation(position);
        TransformGroup scaleAndTrans = new TransformGroup(Transf3D);

        //Comportement billboard: le texte est toujours tourné vers la caméra
        TransformGroup billboardTG = new TransformGroup();
        billboardTG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        billboardTG.addChild(new Shape3D (text3D,TextApp));

        Billboard billboard =
            new Billboard(billboardTG,
                          Billboard.ROTATE_ABOUT_POINT,
                          new Point3f(0.0f, 0.0f, 1.0f));
        billboard.setSchedulingBounds(new BoundingSphere());

        scaleAndTrans.addChild(billboard);
        scaleAndTrans.addChild(billboardTG);

        return scaleAndTrans;
    }

    private TransformGroup selectArrows(Vector3f position, Color color, float sizeFactor, RenderingAttributes rendering) {
        float offset = 0.3f;
        float dist = objSize*20;
        //Apparence tu texte
        Appearance TextApp = new Appearance();
        //Permet de rendre l'objet invisible si necessaire
        if(rendering!=null) TextApp.setRenderingAttributes(rendering);

        TextApp.setColoringAttributes(new ColoringAttributes(
                                            new Color3f(color),
                                            ColoringAttributes.SHADE_FLAT));

        //Police
        Font3D font3D = new Font3D(new Font("Arial", Font.PLAIN, 1),
                                   new FontExtrusion());

        Text3D upArrow = new Text3D(font3D, "▲",
                           new Point3f(0.0f, -dist-offset, 0.0f),
                           Text3D.ALIGN_CENTER,
                           Text3D.PATH_RIGHT);
        Text3D downArrow = new Text3D(font3D, "▼",
                           new Point3f(0.0f, dist-offset, 0.0f),
                           Text3D.ALIGN_CENTER,
                           Text3D.PATH_RIGHT);
        Text3D rightArrow = new Text3D(font3D, "►",
                           new Point3f(-dist, 0.0f-offset, 0.0f),
                           Text3D.ALIGN_CENTER,
                           Text3D.PATH_RIGHT);
        Text3D leftArrow = new Text3D(font3D, "◄",
                           new Point3f(dist, 0.0f-offset, 0.0f),
                           Text3D.ALIGN_CENTER,
                           Text3D.PATH_RIGHT);
        //Reduit (ou agrandit) la taille du texte + translation
        Transform3D Transf3D = new Transform3D();
        Transf3D.setScale(sizeFactor);
        Transf3D.setTranslation(position);
        TransformGroup scaleAndTrans = new TransformGroup(Transf3D);

        //Comportement billboard: le texte est toujours tourné vers la caméra
        TransformGroup billboardTG = new TransformGroup();
        billboardTG.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        billboardTG.addChild(new Shape3D (upArrow,TextApp));
        billboardTG.addChild(new Shape3D (downArrow,TextApp));
        billboardTG.addChild(new Shape3D (rightArrow,TextApp));
        billboardTG.addChild(new Shape3D (leftArrow,TextApp));

        Billboard billboard =
            new Billboard(billboardTG,
                          Billboard.ROTATE_ABOUT_POINT,
                          new Point3f(0.0f, 0.0f, 0.0f));
        billboard.setSchedulingBounds(new BoundingSphere());

        scaleAndTrans.addChild(billboard);
        scaleAndTrans.addChild(billboardTG);

        return scaleAndTrans;
    }

    private void menuGridAction(){
        gridRendering.setVisible(!gridRendering.getVisible());
        if (gridRendering.getVisible())
            menuGrid.setText("Hide grid");
        else
            menuGrid.setText("Show grid");
    }

    private void menuAxisAction(){
        axisRendering.setVisible(!axisRendering.getVisible());
        if (axisRendering.getVisible())
            menuAxis.setText("Hide axis");
        else
            menuAxis.setText("Show axis");
    }

    private void mouseReleasedAction(MouseEvent e){
        if (e.getButton() == MouseEvent.BUTTON3) {
            popupMenu.show(e.getComponent(), e.getX(), e.getY());
        }
    }

    private void zoomSliderAction(){
        float value = zoomSlider.getValue()/100f;
        Transform3D cameraTransform = new Transform3D();
        cameraTransform.setTranslation(new Vector3f(0.0f, 0.0f, 14.0f*value));
        cameraTG.setTransform(cameraTransform);
    }

    public void updateCentroidSelection(boolean[] centroidSelected){
        for (int i=0;i<centroidSelected.length;i++){
            if (centroidSelected[i]==true)
                centroidsSelectRA[i].setVisible(true);
            else
                centroidsSelectRA[i].setVisible(false);
        }
    }

    void updateActionSelection(int[] selectedActions) {
        System.out.println("updateActionSelection called. Actions Selected:");
        for (int j=0;j<selectedActions.length;j++)
            System.out.println(selectedActions[j]);
        for (int i=0;i<NActions;i++){
            boolean visible = false;
            for (int j=0;j<selectedActions.length;j++){
                if (selectedActions[j] == i)
                    visible = true;
            }
            actionsSelectRA[i].setVisible(visible);
        }
    }
}
